package vm

import (
	"os"
	"fmt"
	"sync"
	"reflect"
	"kumachan/standalone/rx"
	. "kumachan/interpreter/runtime/rt"
)


type Context = *rx.Context

type Frame struct {
	Context    Context
	Machine    *Machine
	Function   UsualFunction
	Arguments  [MaxArguments] Value
}

func assert(ok bool, msg string) {
	if !(ok) { panic(msg) }
}
func wrapPanic(e interface{}, ip LocalAddr, frame *Frame) interface{} {
	var cancel, is_cancel = e.(ExecutionCancelled)
	if is_cancel {
		return cancel
	}
	var nested, is_nested = e.(*RuntimeError)
	var content = (func() interface {} {
		if is_nested {
			return "(caller frame)"
		} else {
			return e
		}
	})()
	var err = &RuntimeError {
		Content:   content,
		FrameAddr: reflect.ValueOf(frame).Pointer(),
		Location:  &ExecutionLocation {
			Function: frame.Function.Entity,
			InstPtr:  ip,
		},
	}
	_, _ = fmt.Fprintf(os.Stderr, "%s*\n", err.ConsoleText())
	if is_nested {
		return nested
	} else {
		return err
	}
}
func shiftClosureContextVector(context *([] Value)) {
	*context = append(*context, nil)
	for i := 0; i < len(*context); i += 1 {
		var r = ((len(*context) - 1) - i)
		if r > 0 {
			(*context)[r] = (*context)[r-1]
		}
	}
	(*context)[0] = nil
}

func call(ctx Context, m *Machine, f UsualFunction, arg Value) Value {
	if ctx.AlreadyDisposed() {
		panic(ExecutionCancelled {})
	}
	var frame Frame
	frame.Context = ctx
	frame.Machine = m
	frame.Function = f
	var mapping = f.Entity.Code.ArgsIndex1Vector()
	if len(mapping) > MaxArguments { panic("too many arguments") }
	for i, j := range mapping {
		if j == 0 {
			frame.Arguments[i] = arg
		} else {
			frame.Arguments[i] = arg.(TupleValue).Elements[j-1]
		}
	}
	var tree_size = len(frame.Function.Entity.Code.InstTree)
	if tree_size == 0 { panic("empty instruction tree") }
	var last = LocalAddr(tree_size - 1)
	return eval(last, &frame)
}

func evalVec(ip LocalAddr, frame *Frame) ([] Value) {
	var n, is_size = eval(ip, frame).(LocalSize)
	if !(is_size) { panic("invalid instruction vector") }
	var base = (ip + 1)
	var values = make([] Value, n)
	for i := LocalAddr(0); i < n; i += 1 {
		var item_ip = (base + i)
		var item = &(values[i])
		*item = eval(item_ip, frame)
	}
	return values
}

func evalVecParallel(ip LocalAddr, frame *Frame) ([] Value) {
	var n, is_size = eval(ip, frame).(LocalSize)
	if !(is_size) { panic("invalid instruction vector") }
	if n == 0 { return [] Value {} }
	var base = (ip + 1)
	var values = make([] Value, n)
	var wg sync.WaitGroup
	wg.Add(int(n - 1))
	for i := LocalAddr(1); i < n; i += 1 {
		var item_ip = (base + i)
		var item = &(values[i])
		frame.Machine.pool.Execute(func() {
			*item = eval(item_ip, frame)
			wg.Done()
		})
	}
	values[0] = eval((base + 0), frame)
	wg.Wait()
	return values
}

func evalCall(f FuncValue, arg Value, ip LocalAddr, frame *Frame) Value {
	switch f := (*f).(type) {
	case UsualFunction:
		return call(frame.Context, frame.Machine, f, arg)
	case NativeFunction:
		var l = &ExecutionLocation {
			Function: frame.Function.Entity,
			InstPtr:  ip,
		}
		var h = InteropHandle {
			context:  frame.Context,
			machine:  frame.Machine,
			location: l,
		}
		return f(arg, h)
	default:
		panic("invalid function value")
	}
}

func eval(ip LocalAddr, frame *Frame) Value {
	defer (func() {
		var e = recover()
		if e != nil {
			panic(wrapPanic(e, ip, frame))
		}
	})()
	var code = &(frame.Function.Entity.Code)
	var inst = code.Instruction(ip)
	var op = inst.OpCode
	switch op {
	case SIZE:
		return inst.ToSize()
	case ARG:
		return frame.Arguments[inst.Idx]
	case STATIC:
		return code.StaticValue(inst.Src)
	case CTX:
		return frame.Function.Context[inst.Src]
	case NIL:
		return nil
	case FWD:
		return eval(inst.Obj, frame)
	case ENUM:
		return &ValEnum {
			Index: inst.Idx,
			Value: eval(inst.Obj, frame),
		}
	case SWITCH:
		var obj = eval(inst.Obj, frame)
		var enum = obj.(EnumValue)
		var key = MakeBranchKeySingleItem(enum.Index)
		var target = code.GetBranchAddr(inst.ExtIdx, key)
		var branch = eval(target, frame)
		var f = (*(branch.(FuncValue))).(UsualFunction)
		return call(frame.Context, frame.Machine, f, enum.Value)
	case SELECT:
		var objects = evalVec(inst.Obj, frame)
		var num_of_objects = uint(len(objects))
		assert(num_of_objects <= MaxSelectOperands, "SELECT: too many operands")
		var indexes = make([] ShortIndex, num_of_objects)
		var values = make([] Value, num_of_objects)
		for n := uint(0); n < uint(num_of_objects); n += 1 {
			var enum = objects[n].(EnumValue)
			indexes[n] = enum.Index
			values[n] = enum.Value
		}
		var values_tuple = TupleOf(values)
		var key = MakeBranchKey(indexes)
		var target = code.GetBranchAddr(inst.ExtIdx, key)
		var branch = eval(target, frame)
		var f = (*(branch.(FuncValue))).(UsualFunction)
		return call(frame.Context, frame.Machine, f, values_tuple)
	case CR:
		var enum = eval(inst.Obj, frame).(EnumValue)
		return CaseRef(enum, inst.Idx)
	case CRC:
		var base_ref = eval(inst.Obj, frame)
		return CaseRefFromCaseRef(base_ref, inst.Idx)
	case CRP:
		var base_ref = eval(inst.Obj, frame)
		return CaseRefFromProjRef(base_ref, inst.Idx)
	case TV:
		return TupleOf(evalVec(inst.Obj, frame))
	case TVP:
		return TupleOf(evalVecParallel(inst.Obj, frame))
	case GET:
		var tuple = eval(inst.Obj, frame).(TupleValue)
		return tuple.Elements[inst.Idx]
	case SET:
		var tuple = eval(inst.Obj, frame).(TupleValue)
		var old_elements = tuple.Elements
		var updated_elements = evalVec(inst.Src, frame)
		var indexes = code.GetUpdateIndexes(inst.ExtIdx)
		if len(updated_elements) != len(indexes) {
			panic("SET: arguments and indexes not matching")
		}
		var new_elements = make([] Value, len(old_elements))
		copy(new_elements, old_elements)
		for i, updated := range updated_elements {
			new_elements[indexes[i]] = updated
		}
		return TupleOf(new_elements)
	case PR:
		var tuple = eval(inst.Obj, frame).(TupleValue)
		return FieldRef(tuple, inst.Idx)
	case PRP:
		var base_ref = eval(inst.Obj, frame)
		return FieldRefFromProjRef(base_ref, inst.Idx)
	case CI:
		var ptr = inst.Src
		var obj = eval(inst.Obj, frame)
		return &ValInterface {
			ConcreteValue: obj,
			DispatchTable: code.DispatchTable(ptr),
		}
	case CII:
		var ptr = inst.Src
		var iv = eval(inst.Obj, frame).(InterfaceValue)
		var t = iv.DispatchTable
		var path = code.InterfaceTransformPath(ptr)
		for _, index := range path {
			t = t.Included[index]
		}
		return &ValInterface {
			ConcreteValue: iv.ConcreteValue,
			DispatchTable: t,
		}
	case ICR:
		var iv = eval(inst.Obj, frame).(InterfaceValue)
		var t = code.DispatchTable(inst.Src)
		return InterfaceCaseRef(iv, t)
	case ICRC:
		var base_ref = eval(inst.Obj, frame)
		var t = code.DispatchTable(inst.Src)
		return InterfaceCaseRefFromCaseRef(base_ref, t)
	case ICRP:
		var base_ref = eval(inst.Obj, frame)
		var t = code.DispatchTable(inst.Src)
		return InterfaceCaseRefFromProjRef(base_ref, t)
	case MET:
		var iv = eval(inst.Obj, frame).(InterfaceValue)
		var f = iv.DispatchTable.Methods[inst.Idx]
		var arg = iv.ConcreteValue
		return evalCall(f, arg, ip, frame)
	case LSV:
		return evalVec(inst.Obj, frame)
	case LSC:
		var objects = evalVec(inst.Obj, frame)
		var item_t = GetCompactArrayItemType(inst.Idx)
		var slice_t = reflect.SliceOf(item_t)
		var length = len(objects)
		var rv = reflect.MakeSlice(slice_t, length, length)
		for index, item := range objects {
			rv.Index(index).Set(reflect.ValueOf(item))
		}
		var compact = rv.Interface()
		return compact
	case MPS:
		panic("not implemented")  // TODO
	case MPI:
		panic("not implemented")  // TODO
	case CL, CLR:
		var ptr = inst.Src
		var entity = code.Lambda(ptr)
		var context = evalVec(inst.Obj, frame)
		var required = entity.Code.ContextSize()
		assert(len(context) == int(required), "CL: invalid context length")
		if op == CLR { shiftClosureContextVector(&context) }
		var closure = ValFuncUsual(UsualFunction {
			Entity:  entity,
			Context: context,
		})
		if op == CLR { context[0] = closure }
		return closure
	case INJ:
		var f = eval(inst.Src, frame).(FuncValue)
		var context = evalVec(inst.Obj, frame)
		var closure Value
		switch f := (*f).(type) {
		case UsualFunction:
			var required = f.Entity.Code.ContextSize()
			assert(len(context) == int(required), "INJ: invalid context length")
			assert(len(f.Context) == 0, "INJ: operand is already a closure")
			closure = ValFuncUsual(UsualFunction {
				Entity:  f.Entity,
				Context: context,
			})
		case NativeFunction:
			closure = ValFuncNative(func(arg Value, h InteropContext) Value {
				var arg_with_context = Tuple(arg, context)
				return f(arg_with_context, h)
			})
		default:
			panic("invalid function value")
		}
		return closure
	case CALL:
		var f = eval(inst.Obj, frame).(FuncValue)
		var arg = eval(inst.Src, frame)
		return evalCall(f, arg, ip, frame)
	default:
		panic(fmt.Sprintf("invalid instruction at %d", ip))
	}
}


